package com.gitee.fanx.rpc.server;

import com.fasterxml.jackson.databind.JsonNode;
import com.gitee.fanx.rpc.*;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.Assert;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;

/**
 * @author fanhang
 */
public class RpcHandlerServlet extends HttpServlet {
	private final Logger log = LoggerFactory.getLogger(RpcHandlerServlet.class);

	private final Map<String, Route> routeMap = new HashMap<>(128);

	public void add(Class<?> apiClazz, String methodName, Route route) {
		String routeName = RpcConst.routeName(apiClazz, methodName);
		Assert.state(!this.routeMap.containsKey(routeName), "exists " + routeName);
		this.routeMap.put(routeName, route);
		log.info("rpcMethod [{}] -> {}", routeName, route);
	}

	@Override
	protected void service(HttpServletRequest request, HttpServletResponse response) throws IOException {
		response.setHeader(RpcConst.CONTENT_TYPE, RpcConst.RPC_CONTENT_TYPE);
		try {
			Object result = this.execute(request);
			response.getOutputStream().write(RpcCodec.encode(result));
		} catch (RpcException e) {
			response.setStatus(e.getStatusCode());
			response.getOutputStream().write(RpcCodec.encodeException(e));
		} catch (InvocationTargetException e) {
			if (e.getTargetException() instanceof RpcException rpcException) {
				response.setStatus(rpcException.getStatusCode());
				response.getOutputStream().write(RpcCodec.encodeException(rpcException));
			} else {
				log.error("RPC execute InvocationTargetException", e.getTargetException());
				response.setStatus(500);
				response.getOutputStream().write(RpcCodec.encodeException(e));
			}
		} catch (IOException e) {
			log.error("RPC execute IOException", e);
			throw e;
		} catch (Exception e) {
			log.error("RPC execute unknown Exception", e);
			response.setStatus(500);
			response.getOutputStream().write(RpcCodec.encodeException(e));
		}
	}

	private Object execute(HttpServletRequest request) throws InvocationTargetException, IllegalAccessException, IOException {
		String action = request.getParameter(RpcConst.ACTION_NAME);
		log.debug("request action: {}", action);
		if (action == null || action.isBlank()) {
			throw new RpcException(400, "invalided request action parameter", "");
		}
		Route route = this.routeMap.get(action);
		log.debug("request [{}] route: {}", action, route);
		if (route == null) {
			throw new RpcException(404, "RPC Route not found", request.getRequestURI());
		}
		JsonNode body = RpcCodec.readTree(request.getInputStream().readAllBytes());
		log.debug("request [{}] body: {}", action, body);
		return route.handle(body);
	}
}
